package com.amos.doublewriterconsistence.thread;

import com.amos.doublewriterconsistence.request.Request;
import com.amos.doublewriterconsistence.request.RequestQueue;

import java.util.concurrent.*;

/**
 * Copyright © 2018 五月工作室. All rights reserved.
 *
 * @Package com.amos.common.thread
 * @ClassName RequestThreadPool
 * @Description 请求处理线程池：单例
 * 1. 使用线程池来管理线程，该线程池必须是单例的
 * 2. 线程池初始化成功后，创建缓存队列，并且和线程池进行绑定
 * @Author Amos
 * @Modifier
 * @Date 2019/7/14 16:47
 * @Version 1.0
 **/
public class RequestThreadPool {

    // 在实际项目中，你设置线程池大小是多少，每个线程监控的那个内存队列的大小是多少
    // 都可以做到一个外部的配置文件中
    // 我们这了就给简化了，直接写死了，好吧


    /**
     * 核心线程数
     */
    private Integer corePoolSize = 10;
    /**
     * 线程池最大线程数
     */
    private Integer maximumPoolSize = 20;

    /**
     * 线程最大存活时间
     */
    private Long keepAliveTime = 60L;

    /**
     * 初始化线程池 这里我们不使用Executors.newFixedThreadPool()方式，该种方式不推荐使用，
     * 主要是因为默认允许的队列的长度是Integer.MAX_VALUE,可能会造成OOM
     * 第一个参数：corePoolSize: 线程中核心线程数的最大值（能同时运行的最大的线程数）
     * 第二个参数：maximumPoolSize: 线程池中线程数的最大值
     * 第三个参数：keepAliveTime: 线程存活时间
     * 第四个参数：unit：时间单位
     * 第五个参数：BlockingQueue: 用于缓存任务的队列 这里使用 ArrayBlockingQueue 这个是有界队列
     */
    private ExecutorService threadPool = new ThreadPoolExecutor(this.corePoolSize, this.maximumPoolSize,this.keepAliveTime, TimeUnit.SECONDS,new ArrayBlockingQueue(this.corePoolSize));


    /**
     * 构造器私有化，这样就不能通过new来创建实例对象
     * <p>
     * 类实例化的时候 ，初始化队列的大小，并且绑定队列和线程池以及队列与线程的关系
     * <p>
     * 初始化指定数量的队列
     */
    public RequestThreadPool() {
        /**
         *缓存队列集合来管理所有的缓存队列
         */
        RequestQueue requestQueue = RequestQueue.getInstance();
        for (int i = 0; i < this.corePoolSize; i++) {
            /**
             * 缓存队列使用Request 接口来作为泛型，将可以将队列的类型添加定义，同时也可以通过多态的特性来实现子类的扩展
             * 目前Request只是定义，业务可以之后实现
             *
             **********************************************************************************
             * ArrayBlockingQueue是一个基于数组（有界）的阻塞队列，有界也就意味着，它不能够存储无限多数量的对象。
             * 所以在创建 ArrayBlockingQueue 时，必须要给它指定一个队列的大小。
             */
            ArrayBlockingQueue<Request> queue = new ArrayBlockingQueue<>(this.corePoolSize);
            requestQueue.add(queue);

            //向线程池提交Callable任务，会创建一个新线程(执行任务的线程)去执行这个Callable任务
            System.out.println("使用线程来执行操作---开始执行call()方法!");

            //线程池和缓存队列通过线程来绑定，每个线程对应一个队列
            this.threadPool.submit(new RequestThread(queue));
        }
    }

    /**
     * 使用静态内部类来实现单例的模式（绝对的线程安全）
     *
     * 使用静态内部类的方式来实现单例模式主要有以下的优点：优点：
     * 外部内加载的时候，不需要立即加载内部类，内部类不被加载，就不会初始化，故而不占用内存
     * 当getInstance被调用时，才会去初始化实例，第一次调用getInstance会导致虚拟机加载实例，这种方法不仅能确保线程的安全，也能保证单例的唯一性
     */
    private static class Singleton {
        /**
         * 私有的静态变量，确保该变量不会被外部调用
         */
        private static RequestThreadPool requestThreadPool;

        /**
         * 静态代码块在类初始化时执行一次
         */
        static {
            requestThreadPool = new RequestThreadPool();
        }

        /**
         * 静态内部类对外提供实例的获取方法
         *
         * @return
         */
        public static RequestThreadPool getInstance() {
            return requestThreadPool;
        }
    }

    /**
     * 请求线程池类对外提供获取实例的方法 由于外部类没有RequestThreadPool的实例对象，所以除了该方法，外部类无法创建额外的RequestThreadPool对象
     *
     * @return
     */
    public static RequestThreadPool getInstance() {
        return Singleton.getInstance();
    }


    /**
     * 初始化的便捷方法
     */
    public static void init() {
        getInstance();
    }


    /**
     * 线程池：创建一些线程，使用池化技术来存储这些线程。
     *
     * 使用线程池有如下的好处：
     * 降低资源消耗
     * 可以利用重复已创建的线程降低线程创建和销毁的消耗
     *
     * 提高响应速度
     * 当任务到达时，任务可以不需要等到线程创建就能立即执行
     *
     * 提高线程的可管理性
     * 使用线程池可以进行统一分配、调优和监控
     *
     * 总结：线程池的创建其实说到底就一种实例化的方法,然后通过不同的参数来实现不同效果的
     */
}